package uk.co.q3c.deplan.client.domain.resource;

import java.io.Serializable;
import java.util.Date;
import java.util.logging.Logger;

import javax.persistence.Id;

import uk.co.q3c.deplan.client.domain.AbstractDomainObject;
import uk.co.q3c.deplan.client.domain.util.CalendarAdjustments;
import uk.co.q3c.deplan.client.service.ServiceFactory;

import com.googlecode.objectify.annotation.Cached;
import com.googlecode.objectify.annotation.Unindexed;

/**
 * A set of adjustments to a default or resource calendar
 * 
 * @author DSowerby 26 Apr 2008
 * @see BaseCalendar
 * @see ResourceCalendar
 * 
 */
@Unindexed
@Cached(expirationSeconds = 600)
public abstract class WorkingCalendar extends AbstractDomainObject implements WorkingTime, Serializable {
	protected static final Logger logger = Logger.getLogger(WorkingCalendar.class.getName());
	private static final long serialVersionUID = 1L;
	@Id
	private Long keyId;

	protected WorkingCalendar() {
		super();
	}

	public WorkingCalendar(ServiceFactory connection) {
		super(connection);
	}

	@Override
	protected void beforeLoad() {
		super.beforeLoad();
	}

	protected abstract void createAdjustments();

	/**
	 * @see uk.co.q3c.deplan.client.domain.ControlledObject#dereference()
	 */
	@Override
	public void dereference() {

	}

	public CalendarAdjustment getAdjustment(Date date) {

		return getAdjustments().get(date);
	}

	@SuppressWarnings("deprecation")
	public CalendarAdjustment getAdjustment(int year, int month, int day_of_month) {
		Date d = new Date(0);
		d.setYear(year - 1900);
		d.setMonth(month);
		d.setDate(day_of_month);
		return getAdjustment(d);
	}

	@SuppressWarnings("deprecation")
	public void addAdjustment(int year, int month, int day_of_month, boolean absolute, int adjustment) {
		Date d = new Date(0);

		d.setMonth(month);
		d.setDate(day_of_month);
		d.setYear(year - 1900);
		addAdjustment(d, absolute, adjustment);
	}

	/**
	 * Add an adjustment to working time for a given day. For a resource this is applied after any adjustments made at the base level (which
	 * apply to all resources). Note that there is no <code>removeAdjustment</code>, just call this with <code>absolute==true</code> and
	 * <code>minutes==0</code>
	 * 
	 * @param date
	 *            the date affected. Time is ignored. String must be a valid date for use with DateFormat
	 * 
	 * @param absolute
	 *            if true adjustment is absolute, else it is relative to the baseCalendar
	 * 
	 * @param minutes
	 *            number of minutes by which to adjust (positive or negative)
	 */
	public void addAdjustment(Date date, boolean absolute, int adjustment) {
		CalendarAdjustment caladj = new CalendarAdjustment();
		caladj.setDate(date);
		caladj.setAbsolute(absolute);
		caladj.setAdjustment(adjustment);
		getAdjustments().add(date, caladj);

	}

	public abstract WorkingTime getBaseWorkingTime();

	/**
	 * Returns the working time in minutes for the specified date, or MAXINT if there {@link #baseWorkingTime} is not set. The working time
	 * is the baseWorkingTime offset by adjustments
	 * 
	 * @except
	 * @return the working time in minutes for the specified date
	 */
	@Override
	public int getWorkingTime(Date date) {

		if (date == null) {
			return 0;
		}

		if (getBaseWorkingTime() == null) {
			String msg1 = "baseWorkingTime is null : activation depth may be insufficient";
			// createLog();
			logger.severe(msg1);
			throw new CalendarException(msg1);
		}
		int workingTime = getBaseWorkingTime().getWorkingTime(date);
		CalendarAdjustment ca = getAdjustment(date);

		// no adjustment for this date, just return inherited time
		if (ca == null) {
			return workingTime;
		}
		if (ca.isAbsolute()) {
			workingTime = ca.getAdjustment();
		} else {
			workingTime = workingTime + ca.getAdjustment();
		}
		return workingTime;
	}

	/**
	 * Calculates the duration, in working days, from the minutes given (assumed to be working minutes)
	 * 
	 * @param minutes
	 * @param startingFrom
	 *            TODO
	 * @return
	 */
	public double minutesToWorkingDays(int minutes, Date startingFrom) {
		Date di = new Date(startingFrom.getTime());
		int i = minutes;
		int d = 0;
		double r = 0;
		while (i > 0) {
			int y = getWorkingTime(di);
			if (y > i) {
				r = (double) i / y;
				i = 0;
			} else {
				d++;
				i = i - y;
			}

		}
		double days = d + r;
		return days;
	}

	// @Override
	// public void readXML(XMLImporter importer, Element element) {
	// Element adjusts = element.getChild(ADJUSTMENTS);
	// for (Object child : adjusts.getChildren()) {
	// CalendarAdjustment caladj = new CalendarAdjustment();
	// caladj.readXML(importer, (Element) child);
	// adjustments.add(new Q3Calendar(caladj.getDateIndex().getTime()), caladj);
	//
	// }
	// importer.save(this);
	// }
	//
	// @Override
	// public void writeXML(Element parentElement) {
	// XML.createElement(parentElement, ADJUSTMENTS, adjustments.toString());
	// }

	public int adjustmentCount() {

		return getAdjustments().size();
	}

	public void appendAdjustmentsTo(WorkingCalendar targetCal) {

		targetCal.getAdjustments()
				.add(this.getAdjustments());

	}

	public abstract CalendarAdjustments getAdjustments();

	@Override
	public void afterLoad(ServiceFactory connection) {
		super.afterLoad(connection);
		if (getAdjustments() == null) {
			createAdjustments();
		}

	}

	public Long getKeyId() {
		return keyId;
	}

}
